<template>
  <view :class="['wd-upload', customClass]" :style="customStyle">
    <!-- 预览列表 -->
    <view :class="['wd-upload__preview', customPreviewClass]" v-for="(file, index) in uploadFiles" :key="index">
      <!-- 成功时展示图片 -->
      <view class="wd-upload__status-content">
        <image v-if="isImage(file)" :src="file.url" :mode="imageMode" class="wd-upload__picture" @click="onPreviewImage(file)" />
        <template v-else-if="isVideo(file)">
          <view class="wd-upload__video" v-if="file.thumb" @click="onPreviewVideo(file)">
            <image :src="file.thumb" :mode="imageMode" class="wd-upload__picture" />
            <wd-icon name="play-circle-filled" custom-class="wd-upload__video-paly"></wd-icon>
          </view>
          <view v-else class="wd-upload__video" @click="onPreviewVideo(file)">
            <!-- #ifdef APP-PLUS || MP-DINGTALK -->
            <wd-icon custom-class="wd-upload__video-icon" name="video"></wd-icon>
            <!-- #endif -->
            <!-- #ifndef APP-PLUS -->
            <!-- #ifndef MP-DINGTALK -->
            <video
              :src="file.url"
              :title="file.name || '视频' + index"
              object-fit="contain"
              :controls="false"
              :poster="file.thumb"
              :autoplay="false"
              :show-center-play-btn="false"
              :show-fullscreen-btn="false"
              :show-play-btn="false"
              :show-loading="false"
              :show-progress="false"
              :show-mute-btn="false"
              :enable-progress-gesture="false"
              :enableNative="true"
              class="wd-upload__video"
            ></video>
            <wd-icon name="play-circle-filled" custom-class="wd-upload__video-paly"></wd-icon>
            <!-- #endif -->
            <!-- #endif -->
          </view>
        </template>

        <view v-else class="wd-upload__file" @click="onPreviewFile(file)">
          <wd-icon name="file" custom-class="wd-upload__file-icon"></wd-icon>
          <view class="wd-upload__file-name">{{ file.name || file.url }}</view>
        </view>
      </view>

      <view v-if="file[props.statusKey] !== 'success'" class="wd-upload__mask wd-upload__status-content">
        <!-- loading时展示loading图标和进度 -->
        <view v-if="file[props.statusKey] === 'loading'" class="wd-upload__status-content">
          <wd-loading :type="loadingType" :size="loadingSize" :color="loadingColor" />
          <text class="wd-upload__progress-txt">{{ file.percent }}%</text>
        </view>
        <!-- 失败时展示失败图标以及失败信息 -->
        <view v-if="file[props.statusKey] === 'fail'" class="wd-upload__status-content">
          <wd-icon name="close-outline" custom-class="wd-upload__icon"></wd-icon>
          <text class="wd-upload__progress-txt">{{ file.error || translate('error') }}</text>
        </view>
      </view>
      <!-- 上传状态为上传中时不展示移除按钮 -->
      <wd-icon
        v-if="file[props.statusKey] !== 'loading' && !disabled"
        name="error-fill"
        custom-class="wd-upload__close"
        @click="removeFile(index)"
      ></wd-icon>
      <!-- 自定义预览样式 -->
      <slot name="preview-cover" v-if="$slots['preview-cover']" :file="file" :index="index"></slot>
    </view>

    <block v-if="showUpload">
      <view :class="['wd-upload__evoke-slot', customEvokeClass]" v-if="$slots.default" @click="handleChoose">
        <slot></slot>
      </view>
      <!-- 唤起项 -->
      <view v-else @click="handleChoose" :class="['wd-upload__evoke', disabled ? 'is-disabled' : '', customEvokeClass]">
        <!-- 唤起项图标 -->
        <wd-icon class="wd-upload__evoke-icon" name="fill-camera"></wd-icon>
        <!-- 有限制个数时确认是否展示限制个数 -->
        <view v-if="limit && showLimitNum" class="wd-upload__evoke-num">（{{ uploadFiles.length }}/{{ limit }}）</view>
      </view>
    </block>
  </view>
  <wd-video-preview ref="videoPreview"></wd-video-preview>
</template>

<script lang="ts">
export default {
  name: 'wd-upload',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import wdIcon from '../wd-icon/wd-icon.vue'
import wdVideoPreview from '../wd-video-preview/wd-video-preview.vue'
import wdLoading from '../wd-loading/wd-loading.vue'

import { computed, ref, watch } from 'vue'
import { context, getType, isEqual, isImageUrl, isVideoUrl, isFunction, isDef, deepClone } from '../common/util'
import { chooseFile } from './utils'
import { useTranslate } from '../composables/useTranslate'
import {
  uploadProps,
  type UploadFileItem,
  type ChooseFile,
  type UploadExpose,
  type UploadErrorEvent,
  type UploadChangeEvent,
  type UploadSuccessEvent,
  type UploadProgressEvent,
  type UploadOversizeEvent,
  type UploadRemoveEvent,
  type UploadMethod
} from './types'
import type { VideoPreviewInstance } from '../wd-video-preview/types'

const props = defineProps(uploadProps)

const emit = defineEmits<{
  (e: 'fail', value: UploadErrorEvent): void
  (e: 'change', value: UploadChangeEvent): void
  (e: 'success', value: UploadSuccessEvent): void
  (e: 'progress', value: UploadProgressEvent): void
  (e: 'oversize', value: UploadOversizeEvent): void
  (e: 'chooseerror', value: any): void
  (e: 'remove', value: UploadRemoveEvent): void
  (e: 'update:fileList', value: UploadFileItem[]): void
}>()

defineExpose<UploadExpose>({
  submit: () => startUploadFiles()
})

const { translate } = useTranslate('upload')

const uploadFiles = ref<UploadFileItem[]>([])

const showUpload = computed(() => !props.limit || uploadFiles.value.length < props.limit)

const videoPreview = ref<VideoPreviewInstance>()

watch(
  () => props.fileList,
  (val) => {
    const { statusKey } = props
    if (isEqual(val, uploadFiles.value)) return
    const uploadFileList: UploadFileItem[] = val.map((item) => {
      item[statusKey] = item[statusKey] || 'success'
      item.response = item.response || ''
      return { ...item, uid: context.id++ }
    })
    uploadFiles.value = uploadFileList
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.limit,
  (val) => {
    if (val && val < uploadFiles.value.length) {
      console.error('[wot-design]Error: props limit must less than fileList.length')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforePreview,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforePreview must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.onPreviewFail,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of onPreviewFail must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeRemove,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeRemove must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeUpload,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeUpload must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.beforeChoose,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of beforeChoose must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.buildFormData,
  (fn) => {
    if (fn && !isFunction(fn) && getType(fn) !== 'asyncfunction') {
      console.error('The type of buildFormData must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

function emitFileList() {
  emit('update:fileList', uploadFiles.value)
}

/**
 * 组件内部上传方法
 * @param file 文件
 * @param formData
 * @param options
 */
const upload: UploadMethod = (file, formData, options) => {
  const uploadTask = uni.uploadFile({
    url: options.action,
    header: options.header,
    name: options.name,
    fileName: options.name,
    fileType: options.fileType as 'image' | 'video' | 'audio',
    formData,
    filePath: file.url,
    success(res) {
      if (res.statusCode === options.statusCode) {
        // 上传成功进行文件列表拼接
        options.onSuccess(res, file, formData)
      } else {
        // 上传失败处理
        options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
      }
    },
    fail(err) {
      // 上传失败处理
      options.onError(err, file, formData)
    }
  })
  // 获取当前文件加载的百分比
  uploadTask.onProgressUpdate((res) => {
    options.onProgress(res, file)
  })
}

const startUpload: UploadMethod = (file, formData, options) => {
  const { statusKey, uploadMethod } = props
  // 设置上传中，防止重复发起上传
  file[statusKey] = 'loading'
  if (isFunction(uploadMethod)) {
    uploadMethod(file, formData, options)
  } else {
    upload(file, formData, options)
  }
}

/**
 * 获取图片信息
 * @param img
 */
function getImageInfo(img: string) {
  return new Promise<UniApp.GetImageInfoSuccessData>((resolve, reject) => {
    uni.getImageInfo({
      src: img,
      success: (res) => {
        resolve(res)
      },
      fail: (error) => {
        reject(error)
      }
    })
  })
}

/**
 * @description 初始化文件数据
 * @param {Object} file 上传的文件
 */
function initFile(file: ChooseFile, currentIndex?: number) {
  const { statusKey } = props
  // 状态初始化
  const initState: UploadFileItem = {
    uid: context.id++,
    // 仅h5支持 name
    name: file.name || '',
    thumb: file.thumb || '',
    [statusKey]: 'pending',
    size: file.size || 0,
    url: file.path,
    percent: 0
  }
  if (typeof currentIndex === 'number') {
    uploadFiles.value.splice(currentIndex, 1, initState)
  } else {
    uploadFiles.value.push(initState)
  }
  if (props.autoUpload) {
    startUploadFiles()
  }
}

/**
 *  开始上传文件
 */
function startUploadFiles() {
  const { buildFormData, formData = {}, statusKey } = props
  const { action, name, header = {}, accept, successStatus } = props
  const statusCode = isDef(successStatus) ? successStatus : 200

  for (const uploadFile of uploadFiles.value) {
    // 仅开始未上传的文件
    if (uploadFile[statusKey] == 'pending') {
      if (buildFormData) {
        buildFormData({
          file: uploadFile,
          formData,
          resolve: (formData: Record<string, any>) => {
            formData &&
              startUpload(uploadFile, formData, {
                onSuccess: handleSuccess,
                onError: handleError,
                onProgress: handleProgress,
                action,
                header,
                name,
                fileName: name,
                fileType: accept as 'image' | 'video' | 'audio',
                statusCode
              })
          }
        })
      } else {
        startUpload(uploadFile, formData, {
          onSuccess: handleSuccess,
          onError: handleError,
          onProgress: handleProgress,
          action,
          header,
          name,
          fileName: name,
          fileType: accept as 'image' | 'video' | 'audio',
          statusCode
        })
      }
    }
  }
}

/**
 * @description 上传失败捕获
 * @param {Object} err 错误返回信息
 * @param {Object} file 上传的文件
 */
function handleError(err: Record<string, any>, file: UploadFileItem, formData: Record<string, any>) {
  const { statusKey } = props
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index][statusKey] = 'fail'
    uploadFiles.value[index].error = err.message
    uploadFiles.value[index].response = err
    emit('fail', { error: err, file, formData })
    emitFileList()
  }
}

/**
 * @description 上传成功捕获
 * @param {Object} res 接口返回信息
 * @param {Object} file 上传的文件
 */
function handleSuccess(res: Record<string, any>, file: UploadFileItem, formData: Record<string, any>) {
  const { statusKey } = props
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index][statusKey] = 'success'
    uploadFiles.value[index].response = res.data
    emit('change', { fileList: uploadFiles.value })
    emit('success', { file, fileList: uploadFiles.value, formData })
    emitFileList()
  }
}

/**
 * @description 上传中捕获
 * @param {Object} res 接口返回信息
 * @param {Object} file 上传的文件
 */
function handleProgress(res: UniApp.OnProgressUpdateResult, file: UploadFileItem) {
  const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
  if (index > -1) {
    uploadFiles.value[index].percent = res.progress
    emit('progress', { response: res, file })
  }
}

/**
 * @description 选择文件的实际操作，将chooseFile自己用promise包了一层
 */
function onChooseFile(currentIndex?: number) {
  const { multiple, maxSize, accept, sizeType, limit, sourceType, compressed, maxDuration, camera, beforeUpload } = props
  // 文件选择
  chooseFile({
    multiple,
    sizeType,
    sourceType,
    maxCount: limit ? limit - uploadFiles.value.length : 9,
    accept,
    compressed,
    maxDuration,
    camera
  })
    .then((res) => {
      // 成功选择初始化file
      let files = res
      // 单选只有一个
      if (!multiple) {
        files = files.slice(0, 1)
      }
      // 遍历列表逐个初始化上传参数
      const mapFiles = async (files: ChooseFile[]) => {
        for (let index = 0; index < files.length; index++) {
          const file = files[index]
          if (file.type === 'image' && !file.size) {
            const imageInfo = await getImageInfo(file.path)
            file.size = imageInfo.width * imageInfo.height
          }
          Number(file.size) <= maxSize ? initFile(file, currentIndex) : emit('oversize', { file })
        }
      }

      // 上传前的钩子
      if (beforeUpload) {
        beforeUpload({
          files,
          fileList: uploadFiles.value,
          resolve: (isPass: boolean) => {
            isPass && mapFiles(files)
          }
        })
      } else {
        mapFiles(files)
      }
    })
    .catch((error) => {
      emit('chooseerror', { error })
    })
}

/**
 * @description 选择文件，内置拦截选择操作
 */
function handleChoose(index?: number) {
  if (props.disabled) return
  const { beforeChoose } = props

  // 选择图片前的钩子
  if (beforeChoose) {
    beforeChoose({
      fileList: uploadFiles.value,
      resolve: (isPass: boolean) => {
        isPass && onChooseFile(index)
      }
    })
  } else {
    onChooseFile(index)
  }
}

/**
 * @description 移除文件
 * @param {Object} file 上传的文件
 * @param {Number} index 删除
 */
function handleRemove(file: UploadFileItem) {
  uploadFiles.value.splice(
    uploadFiles.value.findIndex((item) => item.uid === file.uid),
    1
  )
  emit('change', {
    fileList: uploadFiles.value
  })
  emit('remove', { file })
  emitFileList()
}

function removeFile(index: number) {
  const { beforeRemove } = props
  const intIndex: number = index
  const file = uploadFiles.value[intIndex]
  if (beforeRemove) {
    beforeRemove({
      file,
      index: intIndex,
      fileList: uploadFiles.value,
      resolve: (isPass: boolean) => {
        isPass && handleRemove(file)
      }
    })
  } else {
    handleRemove(file)
  }
}

/**
 * 预览文件
 * @param file
 */
function handlePreviewFile(file: UploadFileItem) {
  uni.openDocument({
    filePath: file.url,
    showMenu: true
  })
}

/**
 * 预览图片
 * @param index
 * @param lists
 */
function handlePreviewImage(index: number, lists: string[]) {
  const { onPreviewFail } = props
  uni.previewImage({
    urls: lists,
    current: lists[index],
    fail() {
      if (onPreviewFail) {
        onPreviewFail({
          index,
          imgList: lists
        })
      } else {
        uni.showToast({ title: '预览图片失败', icon: 'none' })
      }
    }
  })
}

/**
 * 预览视频
 * @param index
 * @param lists
 */
function handlePreviewVieo(index: number, lists: UploadFileItem[]) {
  const { onPreviewFail } = props
  // #ifdef MP-WEIXIN
  uni.previewMedia({
    current: index,
    sources: lists.map((file) => {
      return {
        url: file.url,
        type: 'video',
        poster: file.thumb
      }
    }),
    fail() {
      if (onPreviewFail) {
        onPreviewFail({
          index,
          imgList: []
        })
      } else {
        uni.showToast({ title: '预览视频失败', icon: 'none' })
      }
    }
  })
  // #endif

  // #ifndef MP-WEIXIN
  videoPreview.value?.open({ url: lists[index].url, poster: lists[index].thumb, title: lists[index].name })
  // #endif
}

function onPreviewImage(file: UploadFileItem) {
  const { beforePreview, reupload } = props
  const fileList = deepClone(uploadFiles.value)
  const index: number = fileList.findIndex((item) => item.url === file.url)
  const imgList = fileList.filter((file) => isImage(file)).map((file) => file.url)
  const imgIndex: number = imgList.findIndex((item) => item === file.url)
  if (reupload) {
    handleChoose(index)
  } else {
    if (beforePreview) {
      beforePreview({
        file,
        index,
        fileList: fileList,
        imgList: imgList,
        resolve: (isPass: boolean) => {
          isPass && handlePreviewImage(imgIndex, imgList)
        }
      })
    } else {
      handlePreviewImage(imgIndex, imgList)
    }
  }
}

function onPreviewVideo(file: UploadFileItem) {
  const { beforePreview, reupload } = props
  const fileList = deepClone(uploadFiles.value)
  const index: number = fileList.findIndex((item) => item.url === file.url)
  const videoList = fileList.filter((file) => isVideo(file))
  const videoIndex: number = videoList.findIndex((item) => item.url === file.url)
  if (reupload) {
    handleChoose(index)
  } else {
    if (beforePreview) {
      beforePreview({
        file,
        index,
        imgList: [],
        fileList,
        resolve: (isPass: boolean) => {
          isPass && handlePreviewVieo(videoIndex, videoList)
        }
      })
    } else {
      handlePreviewVieo(videoIndex, videoList)
    }
  }
}

function onPreviewFile(file: UploadFileItem) {
  const { beforePreview, reupload } = props
  const fileList = deepClone(uploadFiles.value)
  const index: number = fileList.findIndex((item) => item.url === file.url)
  if (reupload) {
    handleChoose(index)
  } else {
    if (beforePreview) {
      beforePreview({
        file,
        index,
        imgList: [],
        fileList,
        resolve: (isPass: boolean) => {
          isPass && handlePreviewFile(file)
        }
      })
    } else {
      handlePreviewFile(file)
    }
  }
}

function isVideo(file: UploadFileItem) {
  return (file.name && isVideoUrl(file.name)) || isVideoUrl(file.url)
}

function isImage(file: UploadFileItem) {
  return (file.name && isImageUrl(file.name)) || isImageUrl(file.url)
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
